'use strict'

process.chdir(__dirname)

const titbit = require('titbit')
const fs = require('fs')

const app = new titbit({
  debug: true,
  //允许最大body为15M，按照1000字节为1M计算
  maxBody : 15000000,
  
  //启用全局日志
  globalLog: true
})

//计算请求时间的中间件
app.use(async (c, next) => {
  let start_time = Date.now()
  
  await next()

  let end_time = Date.now()

  console.log(c.method, c.path, (end_time - start_time), 'ms')

})

//初始化检测images目录是否存在

try {
  fs.accessSync('./images', fs.constants.F_OK)
} catch (err){
  fs.mkdirSync('./images')
}

//跨域支持的中间件
app.use(async (c, next) => {
  c.setHeader('access-control-allow-origin', '*')

  c.setHeader(
    'access-control-allow-methods',
    ['GET', 'POST', 'OPTIONS', 'DELETE', 'PUT']
  )

  c.setHeader('access-control-allow-headers', 'content-type')

  await next()

})

//用于跨域访问时，OPTIONS请求的处理，
//跨域请求时，在POST、PUT的一些特殊content-type以及DELETE请求时，会先发送OPTIONS请求。
//所以这里通过添加一个OPTIONS请求对任何请求都返回对应的消息头
app.options('/*', async c => {
  //告知浏览器，缓存60秒，60秒内再次发起OPTIONS请求，从本地读取缓存结果即可。
  c.setHeader('cache-control', 'public, max-age=60')
})

app.get('/', async c => {
  
  //设置内容类型
  c.setHeader('content-type', 'text/html; charset=utf-8')

  //返回数据，其中readb接口是对fs.readFile的封装，返回类型为Buffer
  c.res.body = await c.helper.readb('./pages/index.html')
})

app.get('/pid', async c => {
  //返回pid
  c.res.body = `PID: ${process.pid}`
})

app.post('/data', async c => {
  //原样返回
  c.res.body = c.body
})

app.delete('/data', async c => {
  c.res.body = 'ok'
})

//返回上传图片的页面
app.get('/upload', async c => {
  c.setHeader('content-type', 'text/html; charset=utf-8')
  c.res.body = await c.helper.readb('./pages/upload.html')
})


//图片上传检测的中间件
app.use(async (c, next) => {
  let f = c.getFile('image')

  if (f === null) {
    c.status(400)
    c.res.body = 'image not found'
    return
  }

  //检测图片如果大于5000000字节（大概为5M）则返回错误。
  if (f.length > 5000000) {
    c.status(400)
    c.res.body = 'image too large, limit 5000000 bytes.'
    return
  }

  //类型检测
  switch(f['content-type']) {

    case 'image/jpeg':
    case 'image/png':
    case 'image/gif':
    case 'image/webp':
      break

    default:
      c.status(400)
      c.res.body = 'content-type is not supported.'
      return
  }

  await next()

  //针对命名为upload-image的路由执行此中间件
}, {name : 'upload-image'})

//上传图片处理
app.post('/upload', async c => {
  //获取上传文件的信息，是一个对象，
  //包括filename、length、content-type属性
  
  let f = c.getFile('image')

  //解析文件扩展名
  let ext_name = c.helper.extName(f.filename)

  //生成唯一文件名
  let fname = `${c.helper.makeName()}${ext_name}`
  
  //移动文件到目标路径
  await c.moveFile(f, `./images/${fname}`)

  //返回文件名
  c.res.body = fname

    //路由命名为upload-image
}, {name:'upload-image'})


/**
 * images和/image没有必然联系，/image/*是对外暴露给前端的接口，
 * 至于后端要怎么读取，图片保存在哪，则和前端无关。
 * 这样才可以把资源的位置和应用本身分离开，才可以实现解耦。
 * 
 */
//读取图片数据并返回
app.get('/image/*', async c => {
  try {
    //starPath是*表示的路径
    let imgname = c.param.starPath

    let ctype = 'image/jpeg'

    if(imgname.indexOf('.png') > 0) {
      ctype = 'image/png'
    } else if (imgname.indexOf('.gif') > 0) {
      ctype = 'image/gif'
    } else if (imgname.indexOf('.webp') > 0) {
      ctype = 'image/webp'
    }

    c.setHeader('content-type', ctype)

    /**
     * 这种方式来获取图片数据在图片数据不太大的时候，和使用pipe进行流式读取差别并不是特别明显。
     * 优势就是简单，但是缺点也很明显：每次都要读取完之后再一次返回，在大量请求时比较耗内存。
     * 但是可以利用缓存，基于此实现的临时缓存策略则可以比较好的解决这个问题。
     */

    c.res.body = await c.helper.readb(`./images/${imgname}`)

  } catch (err) {
    c.status(404)
  }

})


app.run(1255)